/**
 * Program to run the location server.
 *
 * @author Timothy Flynn (trflynn@wpi.edu)
 */
#include <queue>
#include <list>
#include <pthread.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
using std::queue;
using std::cout;
using std::cerr;
using std::endl;
using std::list;

#include "common.h"
#include "Packet.h"
#include "Frame.h"
#include "DataLinkLayer.h"
#include "PhysicalLayer.h"

// Fuction prototypes
void processCommand(char*);
list<char *> dbqueue;

// Globals to store server data
char *name = NULL;
int numCommand = 0;
int numClient = 0;
int connected = 0;
int terminate = 0;

// Queues to store frames/packets for inter-level comm
queue<Packet *> sendPktQ, recvPktQ;
queue<Frame *> sendFrameQ, recvFrameQ;

// Semaphores to protect access to queues
pthread_mutex_t sendPktM, recvPktM;
pthread_mutex_t sendFrameM, recvFrameM;

// File names
char *dbFileName = "LDatabase.txt";

int main(int argc, char **argv) {
	// Check cmd line args
	if(argc != 1) {
		printf("Usage: %s\n", argv[0]);
		return 1;
	}

	PhysicalLayer *phys = new PhysicalLayer(0.05f);
        DataLinkLayer *data = new DataLinkLayer();

	// Launch DL and PL threads
	pthread_t pt, dt;
        pthread_create(&dt, NULL, runDataLinkLayer, data);
        pthread_create(&pt, NULL, runPhysicalLayer, phys);

	// Run until we receive quit EOF message
	while(1) {
		if(pthread_mutex_trylock(&recvPktM) == 0) {
			Packet *p = NULL;

                        if(!recvPktQ.empty()) {
                                p = recvPktQ.front();
                                recvPktQ.pop();
                                cout << "Server: Got packet! " << p->data << endl;
                        }	
			pthread_mutex_unlock(&recvPktM);

			if(p != NULL) {
				processCommand(p->data);				
				cout << endl;
			}
		}
	}

	return 0;
}

/**
 * Process a command sent from the client.
 *
 * @param head The head of the internal database.
 * @param cmd The command to process.
 * @return The head of the internal database after modification.
 */
void processCommand(char *cmd) {
	char **msg, *header = strtok(cmd, " ");
	int cnt = 0, i;

	// Allocate space for return msg
	msg = (char **)malloc((MAX_DB_SIZE+1) * sizeof(char *));
	for(i=0; i<MAX_DB_SIZE; i++) {
		msg[i] = (char *)malloc(MAX_PACKET_SZ);
	}
	
	// LOGIN command
	if(strcasecmp("login", header) == 0) {
		char *log = strtok(NULL, " ");

		// Make sure all data was given
		if(log == NULL) {
			strcat(msg[cnt++], "Error: usage = login name");
		} else if(strlen(log) > MAX_LOGIN_SIZE) {
			strcat(msg[cnt++], "Error: login must be <= 10 chars");
		}

		// Make sure we're not already logged in
		else if(name == NULL) {
			name = (char *)malloc(MAX_LOGIN_SIZE);
			strcpy(name, log);
		
			strcat(msg[cnt], "Hello ");
			strcat(msg[cnt], name);
			strcat(msg[cnt++], "!");
		} else {
			strcat(msg[cnt], "Error: ");
			strcat(msg[cnt], name);
			strcat(msg[cnt++], " already logged in");
		}
	}	
	
	// Make sure we have someone logged in
	else if(name == NULL) {
		strcat(msg[cnt++], "Error: no one is logged in");
	}

	// ADD command
	else if(strcasecmp("add", header) == 0) {
		char *id = strtok(NULL, " ");
		char *firstName = strtok(NULL, " ");
		char *lastName = strtok(NULL, " ");
		char *location = strtok(NULL, " ");

		// Make sure all data was given
		if(id==NULL || firstName==NULL || lastName==NULL || location==NULL) {
			strcat(msg[cnt++], "Error: usage = add id firstName lastName location");
		} else if(strlen(firstName) > MAX_FIRST_NAME_SIZE) {
			strcat(msg[cnt++], "Error: first name must be <= 20 chars");
		} else if(strlen(lastName) > MAX_LAST_NAME_SIZE) {
			strcat(msg[cnt++], "Error: last name must be <= 25 chars");
		} else if(strlen(location) > MAX_LOCATION_SIZE) {
			strcat(msg[cnt++], "Error: location must be <= 30 chars");
		} else if(strlen(id) != MAX_ID_SIZE) {
			strcat(msg[cnt++], "Error: id must be 9 digits");
		} else {
			int i;
			for(i = 0; i<MAX_ID_SIZE; i++) {
				if(!isdigit(id[i])) {
					strcat(msg[cnt++], "Error: id must be 9 digits");
					break;
				}
			}
		}

		if(cnt == 0 && dbqueue.size() >= MAX_DB_SIZE) {
			strcat(msg[cnt++], "Error: database full, can only hold 100 entries");
		} else if(cnt == 0) {
			dbqueue.push_back(id);

			strcat(msg[cnt], "Added: ");
			strcat(msg[cnt], id); 
			strcat(msg[cnt], " ");
			strcat(msg[cnt], firstName); 
			strcat(msg[cnt], " ");
			strcat(msg[cnt], lastName); 
			strcat(msg[cnt], " ");
			strcat(msg[cnt++], location); 
		}
	}

	// LIST command
	else if(strcasecmp("list", header) == 0) {
		if(dbqueue.size() == 0) {
			strcat(msg[cnt++], "DB empty");
		} 

		for (std::list<char *>::iterator iterator = dbqueue.begin(), end = dbqueue.end(); iterator != end; ++iterator) {
			strcat(msg[cnt++], (*iterator));
		}
	}

	// REMOVE command
	else if(strcasecmp("remove", header) == 0) {
		char *id = strtok(NULL, " ");

		// Make sure all data was given
		if(id == NULL) {
			strcat(msg[cnt++], "Error: usage = remove id");
		}

		// Make sure the given ID actually exists
		else {
			int rem = 0;
			for (list<char *>::iterator iterator = dbqueue.begin(), end = dbqueue.end(); iterator != end; ++iterator) {
                       		if(strcasecmp((*iterator), id) == 0) {
					dbqueue.remove((*iterator));
					rem = 1;
					break;
				}
                	}

			if(rem) {
				strcat(msg[cnt], "Removed: ");
				strcat(msg[cnt++], id);
			} else {
				strcat(msg[cnt], "Error: id ");
				strcat(msg[cnt], id);
				strcat(msg[cnt++], " doesn't exist");
			}
		}
	}

	else {
		strcat(msg[cnt++], "Error: command not recognized");
	}

	// Send the response message(s)
	cout << "Server sending: ";
	for(i=0; i<cnt; i++) {
		Packet  *p2 = (Packet *)malloc(sizeof(Packet));
		p2->end = (i == (cnt-1));
		p2->length = strlen(msg[i]);
		p2->data = (char *)malloc((p2->length+1) * sizeof(char));
		memcpy(p2->data, msg[i], p2->length);
		p2->data[p2->length] = '\0';

		cout << p2->data << " ";

		pthread_mutex_lock(&sendPktM);
		sendPktQ.push(p2);
		pthread_mutex_unlock(&sendPktM);
	}
	cout << endl;
}

